package com.gjm.tool.sign;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 通过Aop的方式实现接口签名
 */
@Order(2)
@Aspect
@Component
public class SignValidatorAspect {

    private static final Logger log = LoggerFactory.getLogger(SignValidatorAspect.class);

    //同一个请求多长时间内有效 10分钟
    private static final Long EXPIRE_TIME = 60 * 1000 * 10L;


    /**
     * 拦截校验相关接口
     */
    @Around("@annotation(com.gjm.tool.sign.Sign)")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        //如果是对外开放的URL, 进行签名校验
        Map<String, String> signatureHeaders = SignValidator(request);
        log.info("签名验证通过, 相关信息: " + signatureHeaders);
        try {
            return pjp.proceed();
        } catch (Throwable e) {
            throw e;
        }
    }

    /**
     * 签名验证
     */
    private Map<String, String> SignValidator(HttpServletRequest request) {
        /**
         * 需要用到的请求参数
         */
        List<String> params = new ArrayList(4);
        params.add(SignUtil.APPID);
        params.add(SignUtil.TIMESTAMP);
        params.add(SignUtil.NONCE);
        params.add(SignUtil.SIGN);
        /**
         * 获取参数和value 生成Map
         */
        Map<String, String> headerMap = Collections.list(request.getHeaderNames())
                .stream()
                .filter(headerName -> params.contains(headerName))
                .collect(Collectors.toMap(headerName -> headerName, headerName -> request.getHeader(headerName)));
        //校验appId
        String appid = headerMap.get(SignUtil.APPID);
        if (StringUtils.isEmpty(appid)) {
            throw new RuntimeException("appid不能为空");
        }
        if (!SignEnum.appidIsInclude(appid)) {
            throw new RuntimeException("非法appid");
        }
        //校验时间戳
        String timestamp = headerMap.get(SignUtil.TIMESTAMP);
        if (StringUtils.isEmpty(timestamp)) {
            throw new RuntimeException("参数timestamp不能为空");
        }
        Long now = System.currentTimeMillis();
        Long requestTimestamp = Long.parseLong(headerMap.get(SignUtil.TIMESTAMP));
        if (requestTimestamp > now || (now - requestTimestamp) > EXPIRE_TIME) {
            String errMsg = "请求时间超过规定范围时间10分钟, timestamp =" + headerMap.get(SignUtil.TIMESTAMP);
            throw new RuntimeException(errMsg);
        }
        //校验随机数
        String nonce = headerMap.get(SignUtil.NONCE);
        if (StringUtils.isEmpty(nonce)) {
            throw new RuntimeException("随机串nonce不能为空");
        }
        if (nonce.length() < 10 || nonce.length() > 30) {
            String errMsg = "随机串nonce长度不合法,长度应为10-20, nonce=" + nonce;
            throw new RuntimeException(errMsg);
        }
        //校验签名
        String sign = headerMap.get(SignUtil.SIGN);
        if (StringUtils.isEmpty(sign)) {
            throw new RuntimeException("参数sign不能为空");
        }
        SortedMap<String, String> sortedMap = new TreeMap<>();
        sortedMap.put(SignUtil.APPID, appid);
        sortedMap.put(SignUtil.TIMESTAMP, timestamp);
        sortedMap.put(SignUtil.NONCE, nonce);
        sortedMap.put(SignUtil.SIGN, sign);
        Boolean flag = SignUtil.isCorrectSign(sortedMap, SignEnum.getAppSecret(appid));
        //比较客户端与服务端签名
        if (!flag) {
            String message = "签名不一致";
            throw new RuntimeException(message);
        }
        return headerMap;
    }
}